package ch.bfh.mgb.hamilton.gui;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.ScrollPaneLayout;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.renderer.category.BarRenderer;
import org.jfree.data.category.DefaultCategoryDataset;

import ch.bfh.mgb.hamilton.simulation.SimulationParameters;
import ch.bfh.mgb.hamilton.simulation.behaviour.DiversificationPopulation;
import ch.bfh.mgb.hamilton.simulation.behaviour.FitnessPopulation;
import ch.bfh.mgb.hamilton.simulation.behaviour.ReproductionFrequencyPopulation;
import ch.bfh.mgb.hamilton.statistics.GenerationDTO;
import ch.bfh.mgb.hamilton.statistics.Statistics;

/**
 * JFrame to display the whole model graphically
 * ..."refactored" to be also the container of the simulation & statistics objects
 */
@SuppressWarnings("serial")
public class Display extends JFrame {

	private SimulationContainer[] sims;
	private int simulationCount;
	
	//ugly coded buttons to perform some actions on them
	final JButton btnStart = new JButton("btnStart");
	final JButton btnStop = new JButton("btnStop");
    
    // row keys...
    private final String series1 = "alive";
    private final String series3 = "born";
    private final String series4 = "starved";
    private final String series5 = "overaged";
    // column keys -> done by generationNumber
	
	public Display() {
		simulationSetup();
	}
	
	private void simulationSetup() {
		sims = new SimulationContainer[2];
		sims[0] = new SimulationContainer("Reproduction Fitness Population", new ReproductionFrequencyPopulation(this, new Statistics()), new DefaultCategoryDataset());
		sims[0].dataset.addValue((Number)0, series1, 0);
		sims[0].dataset.addValue((Number)0, series3, 0);
		sims[0].dataset.addValue((Number)0, series4, 0);
		if (SimulationParameters.overageing) {
			sims[0].dataset.addValue((Number)0, series5, 0);
		}
		
		sims[1] = new SimulationContainer("Food Fitness Population", new FitnessPopulation(this, new Statistics()), new DefaultCategoryDataset());
		sims[1].dataset.addValue((Number)0, series1, 0);
		sims[1].dataset.addValue((Number)0, series3, 0);
		sims[1].dataset.addValue((Number)0, series4, 0);
		if (SimulationParameters.overageing) {
			sims[1].dataset.addValue((Number)0, series5, 0);
		}
		
//		sims[2] = new SimulationContainer("Diversifications", new DiversificationPopulation(this, new Statistics()), new DefaultCategoryDataset());
//		sims[2].dataset.addValue((Number)0, series1, 0);
//		sims[2].dataset.addValue((Number)0, series3, 0);
//		sims[2].dataset.addValue((Number)0, series4, 0);
//		if (SimulationParameters.overageing) {
//			sims[2].dataset.addValue((Number)0, series5, 0);
//		}
		
		simulationCount = sims.length;
	}
	
	public void initDisplay() {
//		this.setSize(850, 800);
		this.setName("Statistis Display");
		this.setTitle("Statistics Display");
		this.addWindowListener(new WindowAdapter() {
			  public void windowClosing(WindowEvent we) {
				    System.exit(0);
				  }
				});
		JPanel borderPanel = new JPanel();
		borderPanel.setLayout(new FlowLayout(FlowLayout.LEFT, 10, 10));
		borderPanel.add(setupMenuPanel());
		borderPanel.add(setupBarChartPanel());
//		this.add(borderPanel.add(setupMenuPanel()), BorderLayout.WEST);
		JScrollPane scrollPane = new JScrollPane(borderPanel);
		this.add(scrollPane);
		//this.add(borderPanel, BorderLayout.CENTER);
		this.pack();
		this.setVisible(true);
	}
	
	private JPanel setupMenuPanel() {
		JPanel panel = new JPanel();
		panel.setLayout(new GridBagLayout());
		GridBagConstraints c = new GridBagConstraints();
		
		c.fill = GridBagConstraints.HORIZONTAL;
		c.gridx = 0;
		int y = 0;
		c.gridy = y++;
		JLabel lblInitPop = new JLabel("initial population");
		lblInitPop.setName("lblInitalPopulation");
		panel.add(lblInitPop, c);		
		
		c.gridy = y++;
		final JTextField txtInitPop = new JTextField("" + SimulationParameters.initialPopulationNumber);
		txtInitPop.setName("txtInitialPopulation");
		txtInitPop.setSize(70, 21);
		panel.add(txtInitPop, c);		
		
		c.gridy = y++;
		JLabel lblIterations = new JLabel("iterations");
		lblIterations.setName("lblIterations");
		panel.add(lblIterations, c);		
		
		c.gridy = y++;
		final JTextField txtIterations = new JTextField("" + SimulationParameters.iterations);
		txtIterations.setName("txtIterations");
		txtIterations.setSize(70, 21);
		panel.add(txtIterations, c);		
		
		c.gridy = y++;
		JLabel lblSmallFood = new JLabel("small food");
		lblSmallFood.setName("lblSmallFood");
		panel.add(lblSmallFood, c);		
		
		c.gridy = y++;
		final JTextField txtSmallFood = new JTextField("" + SimulationParameters.initialSmallFood);
		txtSmallFood.setName("txtSmallFood");
		txtSmallFood.setSize(70, 21);
		panel.add(txtSmallFood, c);		
		
		c.gridy = y++;
		JLabel lblMediumFood = new JLabel("medium food");
		lblMediumFood.setName("lblMediumFood");
		panel.add(lblMediumFood, c);		
		
		c.gridy = y++;
		final JTextField txtMediumFood = new JTextField("" + SimulationParameters.initialMediumFood);
		txtMediumFood.setName("txtMediumFood");
		txtMediumFood.setSize(70, 21);
		panel.add(txtMediumFood, c);		
		
		c.gridy = y++;
		JLabel lblBigFood = new JLabel("big food");
		lblBigFood.setName("lblBigFood");
		panel.add(lblBigFood, c);		
		
		c.gridy = y++;
		final JTextField txtBigFood = new JTextField("" + SimulationParameters.initialBigFood);
		txtBigFood.setName("txtBigFood");
		txtBigFood.setSize(70, 21);
		panel.add(txtBigFood, c);		
		
		c.gridy = y++;
		JLabel lblFoodSearchers = new JLabel("food searchers");
		lblFoodSearchers.setName("lblFoodSearchers");
		panel.add(lblFoodSearchers, c);		
		
		c.gridy = y++;
		final JTextField txtFoodSearchers = new JTextField("" + SimulationParameters.foodSearchers);
		txtFoodSearchers.setName("txtFoodSearchers");
		txtFoodSearchers.setSize(70, 21);
		panel.add(txtFoodSearchers, c);		
		
		c.gridy = y++;
		JLabel lblFoodIterations = new JLabel("food iterations");
		lblFoodIterations.setName("lblFoodIterations");
		panel.add(lblFoodIterations, c);		
		
		c.gridy = y++;
		final JTextField txtFoodIterations = new JTextField("" + SimulationParameters.foodIterations);
		txtFoodIterations.setName("txtFoodIterations");
		txtFoodIterations.setSize(70, 21);
		panel.add(txtFoodIterations, c);		
		
		c.gridy = y++;
		JLabel lblOverageing = new JLabel("overageing");
		lblOverageing.setName("lblOverageing");
		panel.add(lblOverageing, c);		
		
		c.gridy = y++;
		final JCheckBox cbxOverageing = new JCheckBox();
		cbxOverageing.setName("cbxOverageing");
		cbxOverageing.setSize(70, 21);
		cbxOverageing.setSelected(SimulationParameters.overageing);
		panel.add(cbxOverageing, c);			
		
		c.gridy = y++;
		JLabel lblInfinite = new JLabel("infinite generations");
		lblInfinite.setName("lblInfinite");
//		panel.add(lblInfinite, c);		
		
		c.gridy = y++;
		final JCheckBox cbxInfinite = new JCheckBox();
		cbxInfinite.setName("cbxInfinite");
		cbxInfinite.setSize(70, 21);
		cbxInfinite.setSelected(SimulationParameters.infiniteGenerations);
//		panel.add(cbxInfinite, c);	
		

		btnStart.setText("start");
		btnStart.setPreferredSize(new Dimension(70, 21));
		btnStart.setMinimumSize(new Dimension(70, 21));
		btnStart.setMaximumSize(new Dimension(70, 21));
		btnStart.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				txtBigFood.setEditable(false);
				SimulationParameters.initialBigFood = new Integer(txtBigFood.getText());
				txtFoodIterations.setEditable(false);
				SimulationParameters.foodIterations = new Integer(txtFoodIterations.getText());
				txtFoodSearchers.setEditable(false);
				SimulationParameters.foodSearchers = new Integer(txtFoodSearchers.getText());
				txtInitPop.setEditable(false);
				SimulationParameters.initialPopulationNumber = new Integer(txtInitPop.getText());
				txtIterations.setEditable(false);
				SimulationParameters.iterations = new Integer(txtIterations.getText());
				txtMediumFood.setEditable(false);
				SimulationParameters.initialMediumFood = new Integer(txtMediumFood.getText());
				txtSmallFood.setEditable(false);
				SimulationParameters.initialSmallFood = new Integer(txtSmallFood.getText());
				cbxOverageing.setEnabled(false);
				SimulationParameters.overageing = cbxOverageing.isSelected();
				cbxInfinite.setEnabled(false);
				SimulationParameters.infiniteGenerations = cbxInfinite.isSelected();
				btnStart.setEnabled(false);
				
				for (SimulationContainer sc : sims) {
					sc.dataset.clear();
				}
				
				for (final SimulationContainer sc : sims) {
					new Thread(new Runnable() {
						public void run()  {
							Thread.yield();
							sc.simulation.start();
							updateDataset(sc.dataset, sc.simulation.getStatistics());
						}
					}).start();
				}
			}
		});		
		
		c.gridy = y++;
		c.insets = new Insets(20, 0, 0, 0);
		panel.add(btnStart, c);
		
		btnStop.setText("stop");
		btnStop.setPreferredSize(new Dimension(70, 21));
		btnStop.setMinimumSize(new Dimension(70, 21));
		btnStop.setMaximumSize(new Dimension(70, 21));
		btnStop.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				txtBigFood.setEditable(true);
				txtFoodIterations.setEditable(true);
				txtFoodSearchers.setEditable(true);
				txtInitPop.setEditable(true);
				txtIterations.setEditable(true);
				txtMediumFood.setEditable(true);
				txtSmallFood.setEditable(true);
				cbxOverageing.setEnabled(true);
				cbxInfinite.setEnabled(true);
				btnStart.setEnabled(true);

				for (final SimulationContainer sc : sims) {
					sc.simulation.setRunning(false);
				}
			}
		});		
		
		c.gridy = y++;
		c.insets = new Insets(10, 0, 0, 0);
		panel.add(btnStop, c);
		
		return panel;
	}
	
	private JPanel setupBarChartPanel() {
		JPanel chartContainerPanel = new JPanel();
		chartContainerPanel.setLayout(new GridBagLayout());
		GridBagConstraints c = new GridBagConstraints();
		Insets i = new Insets(10, 0, 0, 0);
		c.gridx = 0;
		c.gridy = 0;
		for (SimulationContainer sc : sims) {
	        JFreeChart chart = createChart(sc);
	        ChartPanel cp = new ChartPanel(chart);
	        cp.setPreferredSize(new Dimension(700,350));
	        chartContainerPanel.add(cp, c);
	        c.gridy++;
	        c.insets = i;
		}
        
		return chartContainerPanel;
	}
	
	/**
     * Initial dataset
     */
    private void updateDataset(DefaultCategoryDataset dataset, Statistics statistics) {
        for(GenerationDTO gen : statistics.getGenerations()) {
            dataset.addValue((Number)(gen.livingRobots - gen.newChildren), series1, gen.generationNumber);
            dataset.addValue((Number)gen.newChildren, series3, gen.generationNumber);
            dataset.addValue((Number)(gen.starvedRobots), series4, gen.generationNumber);
    		if (SimulationParameters.overageing) {
                dataset.addValue((Number)(gen.overagedRobots), series5, gen.generationNumber);
    		}
        }
    }
    
    /**
     * Creates a stackedBarChart
     */
    private JFreeChart createChart(SimulationContainer sc) {
        
        // create the chart...
        final JFreeChart chart = ChartFactory.createStackedBarChart(
            sc.name,         // chart title
            "Generation",               // domain axis label
            "Robots",                  // range axis label
            sc.dataset,                  // data
            PlotOrientation.VERTICAL, // orientation
            true,                     // include legend
            true,                     // tooltips?
            false                     // URLs?
        );

        // set the background color for the chart...
        chart.setBackgroundPaint(Color.white);

        // get a reference to the plot for further customisation...
        final CategoryPlot plot = chart.getCategoryPlot();
        plot.setBackgroundPaint(Color.lightGray);
        plot.setDomainGridlinePaint(Color.white);
        plot.setRangeGridlinePaint(Color.white);

        // set the range axis to display integers only...
        final NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis();
        rangeAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());

        // disable bar outlines...
        final BarRenderer renderer = (BarRenderer) plot.getRenderer();
        renderer.setDrawBarOutline(true);

        renderer.setSeriesPaint(0, Color.BLUE);
        renderer.setSeriesPaint(1, Color.RED);
        renderer.setSeriesPaint(2, Color.CYAN);
        renderer.setSeriesPaint(3, Color.LIGHT_GRAY);
        renderer.setSeriesPaint(4, Color.GRAY);

//        final CategoryAxis domainAxis = plot.getDomainAxis();
//        domainAxis.setCategoryLabelPositions(CategoryLabelPositions.createUpRotationLabelPositions(Math.PI / 6.0));
        
        return chart;
    }
    
    public synchronized void performStop() {
    	if (--simulationCount == 0) {
    		
    		btnStop.doClick();
    		simulationCount = sims.length;
    	}
    }
}
